using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace MirrorX
{
    /// <summary>
    /// A simple class to manage the mirroring of one directory into another
    /// </summary>
    class MirrorManager
    {
        /// <summary>
        /// If DoCopy, then actually perform the work; if not, then just
        /// show what would have been done.
        /// </summary>
        bool _doCopy = true;
        public bool DoCopy
        {
            get { return _doCopy; }
            set { _doCopy = value; }
        }

        /// <summary>
        /// Mirror the files in this directory - do nothing
        /// with subdirectories
        /// </summary>
        bool mirrorFiles(string srcDir, string destDir)
        {
            if (!DoCopy)
            {
                if (!Directory.Exists(destDir))
                    return true;
            }

            // Remove deleted files
            string[] destFiles = Directory.GetFiles(destDir);
            foreach (string destFile in destFiles)
            {
                string srcEquiv = reRoot(destFile, srcDir);
                if (!File.Exists(srcEquiv))
                {
                    Console.WriteLine("Delete   : " + destFile);
                    if (DoCopy)
                        File.Delete(destFile);
                }
            }

            // Copy new or changed files
            string[] srcFiles = Directory.GetFiles(srcDir);
            foreach (string srcFile in srcFiles)
            {
                string destEquiv = reRoot(srcFile, destDir);
                bool needCopy = false;

                // If the destination doesn't exist, then add it
                if (!File.Exists(destEquiv))
                {
                    needCopy = true;
                    Console.WriteLine("Add      : " + destEquiv);
                }

                // If the destination does exist, then replace it if the
                // last write dates are different
                if (!needCopy)
                {
                    DateTime srcDate = File.GetLastWriteTime(srcFile);
                    DateTime destDate = File.GetLastWriteTime(destEquiv);

                    if (srcDate > destDate)
                    {
                        needCopy = true;
                        Console.WriteLine("Replace  : " + destEquiv);
                    }
                }

                // If we decided we need to copy, then actually do it
                if (needCopy)
                {
                    if (DoCopy)
                    {
                        File.Copy(srcFile, destEquiv, true);
                    }
                }
            }

            return true;
        }
        
        /// <summary>
        /// Mirror srcDir into destDir.
        /// 
        /// Copies files from srcDir to destDir that don't exist in destDir or
        /// are newer in srcDir.
        /// 
        /// Deletes files from destDir that don't exist in srcDir.
        /// 
        /// This function is called recursively.
        /// </summary>
        /// <param name="srcDir">Full path to the source directory</param>
        /// <param name="destDir">Full path to the destination directory</param>
        /// <returns>success or fail</returns>
        public bool Mirror(string srcDir, string destDir)
        {
            if (!mirrorFiles(srcDir, destDir))
                return false;

            // Look for new directories
            string[] srcDirs = Directory.GetDirectories(srcDir);
            foreach (string srcSubDir in srcDirs)
            {
                string subDirName = Path.GetFileName(srcSubDir);
                string testDest = Path.Combine(destDir, subDirName);
                if (!Directory.Exists(testDest))
                {
                    copyDir(srcSubDir, testDest);
                }
            }

            // Look for directories that only exist in the destination
            // (and remove them)
            if (DoCopy || Directory.Exists(destDir))
            {
                string[] destDirs = Directory.GetDirectories(destDir);
                foreach (string destSubDir in destDirs)
                {
                    string subDirName = Path.GetFileName(destSubDir);
                    string srcEquiv = Path.Combine(srcDir, subDirName);
                    if (!Directory.Exists(srcEquiv))
                    {
                        deleteDir(destSubDir);
                    }
                }
            }

            // Copy the contents of directories
            {
                foreach (string srcSubDir in srcDirs)
                {
                    string subDirName = Path.GetFileName(srcSubDir);
                    string destPath = Path.Combine(destDir, subDirName);
                    if (!Mirror(srcSubDir, destPath))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Given oldName of "c:\foo\bar.txt" and newPath of "c:\baz"
        /// returns "c:\baz\bar.txt".
        /// </summary>
        string reRoot(string oldName, string newPath)
        {
            string subDirName = Path.GetFileName(oldName);
            string destPath = Path.Combine(newPath, subDirName);
            return destPath;
        }

        /// <summary>
        /// Used to copy an entire directory (and subdirectories, recursively)
        /// from srcDir to destDir)
        /// </summary>
        /// <param name="srcDir"></param>
        /// <param name="destDir"></param>
        void copyDir(string srcDir, string destDir)
        {
            Console.WriteLine(string.Format("Add Dir  : {0}", destDir));

            if (DoCopy)
            {
                // Create the target dir if required
                if (!Directory.Exists(destDir))
                    Directory.CreateDirectory(destDir);
            }

            string[] srcFiles = Directory.GetFiles(srcDir);
            foreach (string srcFile in srcFiles)
            {
                string destFile = reRoot(srcFile, destDir);

                Console.WriteLine("Add File : " + destFile);
    
                if (DoCopy) 
                    File.Copy(srcFile, destFile);
            }

            string[] dirNames = Directory.GetDirectories(srcDir);
            foreach (string dirName in dirNames)
            {
                string dirTemp = reRoot(dirName, destDir);
                copyDir(dirName, dirTemp);
            }
        }

        /// <summary>
        /// Delete a directory and all subdirectories and files it contains
        /// (recursively)
        /// </summary>
        void deleteDir(string dirToDelete)
        {
            Console.WriteLine(string.Format("Del Dir  : {0}", dirToDelete));
            if (DoCopy)
            {
                string[] files = Directory.GetFiles(dirToDelete);
                foreach (string file in files)
                {
                    File.SetAttributes(file, FileAttributes.Normal);
                    File.Delete(file);
                }

                string[] dirs = Directory.GetDirectories(dirToDelete);
                foreach (string dir in dirs)
                {
                    deleteDir(dir);
                }

                Directory.Delete(dirToDelete);
            }
        }
    }
}
